fix(helper-auth): rebind app-password on stale device ID instead of returning 409#1
Open
terafin wants to merge 1 commit into
Open
fix(helper-auth): rebind app-password on stale device ID instead of returning 409#1terafin wants to merge 1 commit into
terafin wants to merge 1 commit into
Conversation
0dc6a28 to
09d3d61
Compare
terafin
referenced
this pull request
in intarweb/RetroSaveManager
Jun 7, 2026
…er helper trust The GBA validator requires saves to carry one of the standard library signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V) or an AGB cartridge header. This assumption breaks for **RetroArch's libretro-mGBA core** (the default GBA emulator on Steam Deck / RetroDECK), which does NOT embed those signatures — confirmed against both an EA Sports title (007 - Everything or Nothing) AND a canonical mGBA-targeted save (Pokemon Emerald), neither of which contains any library footer. Net effect today: every legitimately-saved GBA file from a RetroDECK / Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a validated payload signature"`. See issue joeblack2k#7 for full evidence. Fix --- Add `SignatureAdvisoryWithHelperTrust bool` to `strictRawSaveValidationProfile`. When set AND all of `detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank payload hold, the signature check downgrades from reject to warning. The warning surfaces in the inspection record so operators can see the relaxation was applied. Anonymous uploads, uploads without rom_sha1, and blank payloads continue to reject as before. Set the flag on the GBA profile only. Other systems (Gameboy / NES / SNES / NDS) are unaffected. Trust rationale --------------- The advisory downgrade only fires under all three conditions simultaneously: 1. HelperTrusted — the request came over the authenticated helper channel and the helper explicitly asserted the system slug. 2. rom_sha1 present — the helper computed the ROM hash from a local ROM file (not strictly verifiable backend-side but a meaningful signal of provenance). 3. Non-blank payload — rules out freshly-erased FLASH chips and truly-empty save buffers. Random anonymous garbage cannot reach this path. Buggy or malicious clients still fail #1 unless they hold a valid app password. Tests ----- `gba_signature_advisory_test.go` covers: - Regression guard: signature PRESENT — accepted, no advisory warning - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED - Security guard: blank payload under helper trust — REJECTED Full package test suite (15s) passes cleanly with no other regressions. Fixes joeblack2k#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terafin
referenced
this pull request
in intarweb/RetroSaveManager
Jun 8, 2026
…er helper trust The GBA validator requires saves to carry one of the standard library signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V) or an AGB cartridge header. This assumption breaks for **RetroArch's libretro-mGBA core** (the default GBA emulator on Steam Deck / RetroDECK), which does NOT embed those signatures — confirmed against both an EA Sports title (007 - Everything or Nothing) AND a canonical mGBA-targeted save (Pokemon Emerald), neither of which contains any library footer. Net effect today: every legitimately-saved GBA file from a RetroDECK / Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a validated payload signature"`. See issue joeblack2k#7 for full evidence. Fix --- Add `SignatureAdvisoryWithHelperTrust bool` to `strictRawSaveValidationProfile`. When set AND all of `detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank payload hold, the signature check downgrades from reject to warning. The warning surfaces in the inspection record so operators can see the relaxation was applied. Anonymous uploads, uploads without rom_sha1, and blank payloads continue to reject as before. Set the flag on the GBA profile only. Other systems (Gameboy / NES / SNES / NDS) are unaffected. Trust rationale --------------- The advisory downgrade only fires under all three conditions simultaneously: 1. HelperTrusted — the request came over the authenticated helper channel and the helper explicitly asserted the system slug. 2. rom_sha1 present — the helper computed the ROM hash from a local ROM file (not strictly verifiable backend-side but a meaningful signal of provenance). 3. Non-blank payload — rules out freshly-erased FLASH chips and truly-empty save buffers. Random anonymous garbage cannot reach this path. Buggy or malicious clients still fail #1 unless they hold a valid app password. Tests ----- `gba_signature_advisory_test.go` covers: - Regression guard: signature PRESENT — accepted, no advisory warning - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED - Security guard: blank payload under helper trust — REJECTED Full package test suite (15s) passes cleanly with no other regressions. Fixes joeblack2k#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terafin
referenced
this pull request
in intarweb/RetroSaveManager
Jun 8, 2026
…er helper trust The GBA validator requires saves to carry one of the standard library signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V) or an AGB cartridge header. This assumption breaks for **RetroArch's libretro-mGBA core** (the default GBA emulator on Steam Deck / RetroDECK), which does NOT embed those signatures — confirmed against both an EA Sports title (007 - Everything or Nothing) AND a canonical mGBA-targeted save (Pokemon Emerald), neither of which contains any library footer. Net effect today: every legitimately-saved GBA file from a RetroDECK / Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a validated payload signature"`. See issue joeblack2k#7 for full evidence. Fix --- Add `SignatureAdvisoryWithHelperTrust bool` to `strictRawSaveValidationProfile`. When set AND all of `detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank payload hold, the signature check downgrades from reject to warning. The warning surfaces in the inspection record so operators can see the relaxation was applied. Anonymous uploads, uploads without rom_sha1, and blank payloads continue to reject as before. Set the flag on the GBA profile only. Other systems (Gameboy / NES / SNES / NDS) are unaffected. Trust rationale --------------- The advisory downgrade only fires under all three conditions simultaneously: 1. HelperTrusted — the request came over the authenticated helper channel and the helper explicitly asserted the system slug. 2. rom_sha1 present — the helper computed the ROM hash from a local ROM file (not strictly verifiable backend-side but a meaningful signal of provenance). 3. Non-blank payload — rules out freshly-erased FLASH chips and truly-empty save buffers. Random anonymous garbage cannot reach this path. Buggy or malicious clients still fail #1 unless they hold a valid app password. Tests ----- `gba_signature_advisory_test.go` covers: - Regression guard: signature PRESENT — accepted, no advisory warning - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED - Security guard: blank payload under helper trust — REJECTED Full package test suite (15s) passes cleanly with no other regressions. Fixes joeblack2k#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terafin
referenced
this pull request
in intarweb/RetroSaveManager
Jun 8, 2026
…er helper trust The GBA validator requires saves to carry one of the standard library signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V) or an AGB cartridge header. This assumption breaks for **RetroArch's libretro-mGBA core** (the default GBA emulator on Steam Deck / RetroDECK), which does NOT embed those signatures — confirmed against both an EA Sports title (007 - Everything or Nothing) AND a canonical mGBA-targeted save (Pokemon Emerald), neither of which contains any library footer. Net effect today: every legitimately-saved GBA file from a RetroDECK / Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a validated payload signature"`. See issue joeblack2k#7 for full evidence. Fix --- Add `SignatureAdvisoryWithHelperTrust bool` to `strictRawSaveValidationProfile`. When set AND all of `detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank payload hold, the signature check downgrades from reject to warning. The warning surfaces in the inspection record so operators can see the relaxation was applied. Anonymous uploads, uploads without rom_sha1, and blank payloads continue to reject as before. Set the flag on the GBA profile only. Other systems (Gameboy / NES / SNES / NDS) are unaffected. Trust rationale --------------- The advisory downgrade only fires under all three conditions simultaneously: 1. HelperTrusted — the request came over the authenticated helper channel and the helper explicitly asserted the system slug. 2. rom_sha1 present — the helper computed the ROM hash from a local ROM file (not strictly verifiable backend-side but a meaningful signal of provenance). 3. Non-blank payload — rules out freshly-erased FLASH chips and truly-empty save buffers. Random anonymous garbage cannot reach this path. Buggy or malicious clients still fail #1 unless they hold a valid app password. Tests ----- `gba_signature_advisory_test.go` covers: - Regression guard: signature PRESENT — accepted, no advisory warning - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED - Security guard: blank payload under helper trust — REJECTED Full package test suite (15s) passes cleanly with no other regressions. Fixes joeblack2k#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terafin
referenced
this pull request
in intarweb/RetroSaveManager
Jun 8, 2026
…er helper trust The GBA validator requires saves to carry one of the standard library signature strings (EEPROM_V / SRAM_V / FLASH_V / FLASH1M_V / FLASH512_V) or an AGB cartridge header. This assumption breaks for **RetroArch's libretro-mGBA core** (the default GBA emulator on Steam Deck / RetroDECK), which does NOT embed those signatures — confirmed against both an EA Sports title (007 - Everything or Nothing) AND a canonical mGBA-targeted save (Pokemon Emerald), neither of which contains any library footer. Net effect today: every legitimately-saved GBA file from a RetroDECK / Steam Deck user is rejected with `HTTP 422 - "gba raw save is missing a validated payload signature"`. See issue joeblack2k#7 for full evidence. Fix --- Add `SignatureAdvisoryWithHelperTrust bool` to `strictRawSaveValidationProfile`. When set AND all of `detection.Evidence.HelperTrusted` + non-empty `rom_sha1` + non-blank payload hold, the signature check downgrades from reject to warning. The warning surfaces in the inspection record so operators can see the relaxation was applied. Anonymous uploads, uploads without rom_sha1, and blank payloads continue to reject as before. Set the flag on the GBA profile only. Other systems (Gameboy / NES / SNES / NDS) are unaffected. Trust rationale --------------- The advisory downgrade only fires under all three conditions simultaneously: 1. HelperTrusted — the request came over the authenticated helper channel and the helper explicitly asserted the system slug. 2. rom_sha1 present — the helper computed the ROM hash from a local ROM file (not strictly verifiable backend-side but a meaningful signal of provenance). 3. Non-blank payload — rules out freshly-erased FLASH chips and truly-empty save buffers. Random anonymous garbage cannot reach this path. Buggy or malicious clients still fail #1 unless they hold a valid app password. Tests ----- `gba_signature_advisory_test.go` covers: - Regression guard: signature PRESENT — accepted, no advisory warning - The fix: signature missing + helper trust + rom_sha1 + non-blank — accepted with warning - Security guard: signature missing + helper trust but no rom_sha1 — REJECTED - Security guard: signature missing + rom_sha1 but no helper trust — REJECTED - Security guard: blank payload under helper trust — REJECTED Full package test suite (15s) passes cleanly with no other regressions. Fixes joeblack2k#7 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eturning 409 [squashed; see PR body for full description] 🤖 Generated with [Claude Code](https://claude.com/claude-code)
e580cb7 to
50a7709
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Helper save uploads fail with HTTP
409 "app password is already bound to another device"even when the app-password is correctly bound to the requesting device. This breaks uploads forsgm-steamdeck-helper(and any helper):security_device_state.jsonshows the correct binding, yet every upload 409s.Root cause
In
authenticateHelperKey(backend/cmd/server/helper_auth.go), CHECK 3 compares the app-password record's storedBoundDeviceIDagainst the device resolved by fingerprint (findDeviceByIdentityLocked) and returns 409 whenever they differ and the stale device ID still exists in memory:After an RSM restart reloads
security_device_state.json, the same physical machine can come back under a new device ID (re-created / re-keyed / inconsistent state). The storedBoundDeviceIDand the fingerprint-resolvedboundDevice.IDthen legitimately diverge, so this guard 409s on a device that is actually the rightful owner — even though the code immediately below CHECK 3 already knows how to rebind the record to the current device.Fix
CHECK 3 rebind — only return 409 when the stale device is a genuinely different machine (its fingerprint doesn't match the resolved device). Otherwise fall through to the existing rebind path, which repoints the app-password at the current device ID:
Orphan cleanup —
bindAppPasswordToDeviceLockedpreviously unbound a displaced app-password but left the dangling record ina.appPasswordsforever. It now deletes that record when no other device references it (deleteAppPasswordIfOrphanedLocked), preventing unbound/unusable app-passwords from accumulating across repeated (re)bindings.Tests
New
backend/cmd/server/helper_auth_device_binding_test.go:go test ./cmd/server/passes in full;gofmtandgo vetare clean.Notes for reviewers
The original issue report proposed a second root cause — that
bindAppPasswordToDeviceLockednever sets the reciprocaldevice.AppPasswordID. That doesn't apply to the current source: the function already sets the reciprocaldevice.BoundAppPasswordID(security_state.go), andappPasswordIDForDeviceLockedkeys off the app-password'sBoundDeviceID, not a device-side field. The verifiable over-eager 409 is the CHECK 3 path fixed here.🤖 Generated with Claude Code
Update — also fixes the device_type drift (CHECK 1) + tested on real hardware
The stale-device-ID rebind (CHECK 3) alone wasn't enough: the SGM Steam Deck helper
enrolls with
device_type="steamdeck"but uploads saves tagged with the source'semulator type (
device_type="retroarch"), keeping a stablefingerprint. BecauseauthenticateHelperKeykeyed identity on(device_type, fingerprint), every uploadresolved/created a different device and 409'd. This adds a CHECK 1 fix: treat the
fingerprint as the device identity and
device_typeas variable metadata — samefingerprint ⇒ same device (rebind cleanly), different fingerprint ⇒ still rejected.
Tested on real hardware: Steam Deck running
sgm-steamdeck-helperv0.4.16 (RetroDecksource) against a build of this branch. Before: every save
409 Conflict. After:sync … uploaded=3 … errors=0, and a second sync reportsin_sync=3. Added unit testsfor both the same-fingerprint/different-device_type (success) and different-fingerprint
(reject) cases.